#include <map>
#include <string>
#include <iterator>
#include <iostream>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "helper.hpp"
#include "interface.hpp"
#include "tcp_server.hpp"

static int server_sock = -1;
static int port_number = 2324;
static pthread_t tcp_client_thread;

static void* tcp_client_io_handler(void* obj)
{   
    int n;
    std::string gid;
    int client_socket = *((int*)obj);

    char read_buffer[4096];

    while(true){
        bzero(read_buffer,sizeof(read_buffer));
        n = read(client_socket, read_buffer, sizeof(read_buffer));
        if (n <= 0){
            logger(SM_LOG_INFO,"Connection closed by peer.");
            close(client_socket);
            pthread_exit(NULL);
        }
        logger(SM_LOG_INFO,"REQUEST: %s", read_buffer);

        std::string response;
        response = handle_request(read_buffer);

        /* +1 for null terminated character */
        n = write(client_socket, response.c_str(), response.length() + 1); 
        if (n < 0){
            perror("ERROR writing to socket");
            pthread_exit(NULL);
        }
        logger(SM_LOG_INFO,"RESPONSE: %s", response.c_str());
    }
}

static void* tcp_client_handler(void* obj)
{   
    int client_socket;
    struct sockaddr_in client_addr;
    socklen_t client_len;

    client_len = sizeof(client_addr);
    std::vector<pthread_t*> clients_thread;

    while(true){
        client_socket = accept(server_sock, (struct sockaddr *) &client_addr, &client_len);
        if (client_socket < 0){
            perror("ERROR on accepting incoming connection.");
            continue;
        }

        pthread_t *thread = (pthread_t*)malloc(sizeof(pthread_t));
        if(!thread){
            logger(SM_LOG_ERR, "ERROR CREATING NEW TCP CONNECTION, CAN'T ALLOCATE MEMORY.");
            continue;
        }

        for (unsigned int i = 0; i < clients_thread.size(); ++i) {
            if(!pthread_tryjoin_np(*clients_thread[i], NULL)) { // TODO
                if(clients_thread[i]){
                    free(clients_thread[i]);
                }
                clients_thread.erase(clients_thread.begin() +i);
                i = 0;
            }
        }

        clients_thread.push_back(thread);

        logger(SM_LOG_INFO,"CONNECTED FROM %s:%d",
                inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        pthread_create(thread, NULL, &tcp_client_io_handler, (void*)&client_socket);
    }

    pthread_exit(NULL);
}

void init_tcp_server_non_ssl()
{
    int reuse = 1;
    struct sockaddr_in serv_addr;

    server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sock  < 0){
        perror("ERROR opening socket");
        exit(1);
    }

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(port_number);
    if(setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1){
        perror("SO_REUSEADDR");
        exit(1);
    }

    if(setsockopt(server_sock, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) == -1){
        perror("SO_REUSEPORT");
        exit(1);
    }

    if (bind(server_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0){
        perror("bind");
        exit(1);
    }

    listen(server_sock, 100);
    pthread_create(&tcp_client_thread, NULL, &tcp_client_handler, NULL);
}
